package edu.washington.cs.workflow.recommender;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import edu.washington.cs.workflow.analysis.guistructure.EventEntity;
import edu.washington.cs.workflow.analysis.guistructure.EventTraceRecoverer;
import edu.washington.cs.workflow.analysis.guistructure.UIActionEntity;
import edu.washington.cs.workflow.analysis.guistructure.UIActionHandler;
import edu.washington.cs.workflow.analysis.guistructure.UIMethodEntity;
import edu.washington.cs.workflow.util.Utils;

public class EventTraceProcessor {

	public final List<EventEntity> list;
	
	public EventTraceProcessor(String fileName) {
		this(new String[]{fileName});
//		EventTraceRecoverer recoverer = new EventTraceRecoverer(fileName);
//		this.list = recoverer.recoverEventEntities();
	}
	
	public EventTraceProcessor(String[] fileNames) {
		EventTraceRecoverer recoverer = new EventTraceRecoverer(fileNames);
		this.list = recoverer.recoverEventEntities();
	}
	
	public EventTraceProcessor(List<EventEntity> list) {
		Utils.checkNotNull(list);
		this.list = list;
	}
	
	/**Get all handlers and methods */
	public List<String> getAllMethods() {
		List<String> allMethods = new LinkedList<String>();
		for(EventEntity entity : list) {
			if(entity instanceof UIActionHandler) {
				allMethods.add(((UIActionHandler)entity).methodDesc);
			} else if (entity instanceof UIMethodEntity) {
				allMethods.add(((UIMethodEntity)entity).methodDesc);
			}
		}
		return allMethods;
	}
	
	/**Get all unique handlers and methods*/
	public List<String> getAllUniqueMethods() {
		Set<String> set = new LinkedHashSet<String>();
		for(String str : this.getAllMethods()) {
			set.add(str);
		}
		
		List<String> l = new LinkedList<String>();
		l.addAll(set);
		return l;
	}
	
	private Map<String, Integer> methodOccur = null;
	public int getNumOfMethodOccurance(String methodDesc) {
		if(methodOccur != null) {
			return methodOccur.get(methodDesc);
		}
		methodOccur = new LinkedHashMap<String, Integer>();
		for(EventEntity entity : this.list) {
			String desc = null;
			if(entity instanceof UIActionHandler) {
				desc = ((UIActionHandler)entity).methodDesc;
			} else if (entity instanceof UIMethodEntity) {
				desc = ((UIMethodEntity)entity).methodDesc;
			}
			if(desc != null) {
				if(!methodOccur.containsKey(desc)) {
					methodOccur.put(desc, 1);
				} else {
					methodOccur.put(desc, methodOccur.get(desc) + 1);
				}
			}
		}
		return methodOccur.get(methodDesc);
	}
	
	/*with cache*/
	private Map<String, Set<String>> handlerIncluded = null;
	public Set<String> getIncludingHandlers(String methodDesc) {
		if(handlerIncluded != null) {
			return handlerIncluded.get(methodDesc);
		}
		handlerIncluded = new LinkedHashMap<String, Set<String>>();
		
		String currentHandler = null;
		for(EventEntity event : this.list) {
			if(event instanceof UIActionEntity) {
				continue;
			}
			if(event instanceof UIActionHandler) {
				currentHandler = ((UIActionHandler)event).methodDesc;
			}
			if(event instanceof UIMethodEntity) {
				String currentMethodDesc = ((UIMethodEntity)event).methodDesc;
				if(currentHandler != null) {
					if(!handlerIncluded.containsKey(currentMethodDesc)) {
						handlerIncluded.put(currentMethodDesc, new LinkedHashSet<String>());
					}
					handlerIncluded.get(currentMethodDesc).add(currentHandler);
				}
			}
		}
		
		return handlerIncluded.get(methodDesc);
	}
	
	/*with cache*/
	private Map<String, Set<String>> triggerActions = null;
	public Set<String> getTriggeringActions_fast(String methodDesc) {
		if(triggerActions != null) {
			return this.triggerActions.get(methodDesc);
		}
		triggerActions = new LinkedHashMap<String, Set<String>>();
		String currentAction = null;
		for(EventEntity event : this.list) {
			if(event instanceof UIActionEntity) {
				currentAction = ((UIActionEntity)event).actionDesc;
			}
			if(currentAction != null) {
				String desc = null;
				if(event instanceof UIActionHandler) {
					UIActionHandler h = (UIActionHandler)event;
					desc = h.methodDesc;
				} else if (event instanceof UIMethodEntity) {
					UIMethodEntity m = (UIMethodEntity)event;
					desc = m.methodDesc;
				}
				if(desc != null) {
					if(!triggerActions.containsKey(desc)) {
						triggerActions.put(desc, new LinkedHashSet<String>());
					}
					triggerActions.get(desc).add(currentAction);
				}
			}
		}
		return triggerActions.get(methodDesc);
	}
	
	/**Get all handlers*/
	public List<UIActionHandler> getAllActionHandlers() {
		List<UIActionHandler> handlers = new LinkedList<UIActionHandler>();
		for(EventEntity entity : list) {
			if(entity instanceof UIActionHandler) {
				handlers.add((UIActionHandler)entity);
			}
		}
		return handlers;
	}
	
	public List<String> getAllUniqueActionHandlerDescs() {
		Set<String> handlerSet = new LinkedHashSet<String>();
		List<UIActionHandler> handlers = this.getAllActionHandlers();
		for(UIActionHandler handler : handlers) {
			handlerSet.add(handler.methodDesc);
		}
//		return handlerSet;
		List<String> uniqueHandlerList = new LinkedList<String>();
		uniqueHandlerList.addAll(handlerSet);
		return uniqueHandlerList;
	}
	
	/**Get all UI actions*/
	public List<UIActionEntity> getAllUIActions() {
		List<UIActionEntity> actions = new LinkedList<UIActionEntity>();
		for(EventEntity entity : list) {
			if(entity instanceof UIActionEntity) {
				actions.add((UIActionEntity)entity);
			}
		}
		return actions;
	}
	
	public Set<String> getUniqueActionDesc() {
		Set<String> actionSet = new LinkedHashSet<String>();
		for(UIActionEntity action : this.getAllUIActions()) {
			actionSet.add(action.actionDesc);
		}
		return actionSet;
	}
	
	/**In order*/
	public List<String> getInvokedMethodsByAction(UIActionEntity action) {
		return getInvokedMethodsByAction(action.actionDesc);
	}
	
	public List<String> getInvokedMethodsByAction(String actionDesc) {
		List<String> methods = new LinkedList<String>();
		
		Set<String> methodSet = new LinkedHashSet<String>();
		boolean within = false;
		for(EventEntity entity : list) {
			if(entity instanceof UIActionEntity) {
				UIActionEntity action = (UIActionEntity)entity;
				if(action.actionDesc.equals(actionDesc)) {
					within = true;
				} else {
					within = false;
				}
			}
			
			if(within) {
				if(entity instanceof UIMethodEntity) {
					methodSet.add(((UIMethodEntity)entity).methodDesc);
				} else if(entity instanceof UIActionHandler){
					methodSet.add(((UIActionHandler)entity).methodDesc);
				}
			}
		}
		methods.addAll(methodSet);
		return methods;
	}
	
	/**
	 * A simple handler may exist multiple times in the trace
	 * */
	public List<String> getInvokedMethodsByHandler(UIActionHandler handler) {
		return getInvokedMethodsByHandler(handler.methodDesc);
	}
	public List<String> getInvokedMethodsByHandler(String handlerMethodDesc) {
		List<String> entities = new LinkedList<String>();
		
		Set<String> methods = new LinkedHashSet<String>();
		boolean within = false; //is the method invoked by the handler
		for(EventEntity entity : list) {
			if(entity instanceof UIActionHandler) {
				UIActionHandler h = (UIActionHandler)entity;
				if(h.methodDesc.equals(handlerMethodDesc)) {
					if(h.isEntry) {
					    within = true;
					} else {
						within = false;
					}
				} 
			}
			if(within) {
				if(entity instanceof UIActionHandler) {
					UIActionHandler h = (UIActionHandler)entity;
					if(!h.methodDesc.equals(handlerMethodDesc)) {
						methods.add(h.methodDesc);
					}
				} else if (entity instanceof UIMethodEntity) {
					UIMethodEntity me = (UIMethodEntity)entity;
					methods.add(me.methodDesc);
				}
			}
		}
		
		entities.addAll(methods);
		return entities;
	}
	
	/**
	 * Fast lookup by using caching
	 * */
	Map<String, Set<String>> handlerMethods = null;
	public List<String> getInvokedMethodsByHandler_fast(String handlerMethodDesc) {
		if(handlerMethods == null) {
			//create the handler methods
			handlerMethods = new LinkedHashMap<String, Set<String>>();
			String currentHandler = null;
			for(EventEntity event : this.list) {
				if(event instanceof UIActionEntity) {
					continue;
				} else if (event instanceof UIActionHandler) {
					currentHandler = ((UIActionHandler)event).methodDesc;
					if(!handlerMethods.containsKey(currentHandler)) {
						handlerMethods.put(currentHandler, new LinkedHashSet<String>());
					}
				}
				
				if(currentHandler != null) {
					if(event instanceof UIMethodEntity) {
						String methodDesc = ((UIMethodEntity)event).methodDesc;
						Utils.checkTrue(handlerMethods.containsKey(currentHandler));
						handlerMethods.get(currentHandler).add(methodDesc);
					}
				}
			}
		}
		
		List<String> l = new LinkedList<String>();
		if(handlerMethods.containsKey(handlerMethodDesc)) {
		    l.addAll(handlerMethods.get(handlerMethodDesc));
		}
		return l;
	}
	
	/**
	 * Reverse query
	 * */
	private List<UIActionEntity> getTriggeringActions(String methodDesc) {
		List<UIActionEntity> actions = new LinkedList<UIActionEntity>();
		
		Set<UIActionEntity> actionSet = new LinkedHashSet<UIActionEntity>();
		UIActionEntity currentAction = null;
		for(EventEntity entity : list) {
			if(entity instanceof UIActionEntity) {
				currentAction = (UIActionEntity)entity;
			}
			//if there is a current action
			if(currentAction != null) {
				if(entity instanceof UIMethodEntity) {
					if(methodDesc.equals(((UIMethodEntity)entity).methodDesc)) {
						actionSet.add(currentAction);
					}
				} else if (entity instanceof UIActionHandler) {
					if(methodDesc.equals(((UIActionHandler)entity).methodDesc)) {
						actionSet.add(currentAction);
					}
				}
			}
		}
		
		actions.addAll(actionSet);
		
		return actions;
	}
	
	public List<String> getTriggeringActionDescs(String methodDesc) {
		List<String> actionSet = new LinkedList<String>();
		for(UIActionEntity action : this.getTriggeringActions(methodDesc)) {
			actionSet.add(action.actionDesc);
		}
		return actionSet;
	}
}
